/*
 *     *********************************************************************
 *     * Copyright (C) 1988, 1990 Stanford University.                     *
 *     * Permission to use, copy, modify, and distribute this              *
 *     * software and its documentation for any purpose and without        *
 *     * fee is hereby granted, provided that the above copyright          *
 *     * notice appear in all copies.  Stanford University                 *
 *     * makes no representations about the suitability of this            *
 *     * software for any purpose.  It is provided "as is" without         *
 *     * express or implied warranty.  Export of this software outside     *
 *     * of the United States of America may require an export license.    *
 *     *********************************************************************
 */

/*
 * Window Manager for logic analyzer
 *
 */
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "ana.h"
#include <X11/Xutil.h>
#include <X11/Xlib.h>
#include "graphics.h"
#include "ana_glob.h"


public	Display     *display = NULL;
public	Screen      *screen;
public	Window      window = 0;
public	Window      iconW = 0;

public	int         CHARHEIGHT = 0;
public	int         CHARWIDTH = 0;
public	int	    descent;

public	Times       tims;
public	Traces      traces;
public	Wstate      windowState = { FALSE, FALSE, FALSE };

private	const char * wname = "analyzer";
public	const char * banner;
public	int          bannerLen;

void PRINTF( const char * fmt, ... );

private void DrawVector( Trptr t, TimeType t1, TimeType t2, int clr_bg );
private void DrawSignal( Trptr t, TimeType t1, TimeType t2 );
void MoveCursorToPos( Coord x );

private	void    EraseCursor(), DrawCursor();


/*
 * Convert a time to its corresponding x position.
 */
public
#define TimeToX( tm )							\
    (((tm) - tims.start) * (traceBox.right - traceBox.left - 2) /	\
      tims.steps + traceBox.left + 1 )


/*
 * Convert an x position to the closest time-step.
 * Return -1 if the point lies outside the traces window.
 */
public TimeType XToTime( Coord  x ) {
   float  tmp;

   if( ( x <= traceBox.left ) or ( x >= traceBox.right ) )
      return( -1 );
   tmp = ( float ) tims.steps / ( traceBox.right - traceBox.left - 2 );
   return( tims.start + round( ( x - traceBox.left - 1 ) * tmp ) );
}


/*
 * return TRUE if the 2 bounding boxes intersect, otherwise FALSE
 */
#define Intersect( b1, b2 )						\
    ( ( (b1.top > b2.bot) or (b2.top > b1.bot) or			\
      (b1.left > b2.right) or (b2.left > b1.right ) ) ? FALSE : TRUE )


/*
 * Initialize the windows and various other metrics.
 */
public int InitDisplay( char * fname, char * display_unit ) {
   XFontStruct  *font;

   if( display == NULL ) {
      if( ( display = XOpenDisplay( display_unit ) ) == NULL ) {
         fprintf( stderr, "could not open display\n" );
         return( FALSE );
      }
      screen = ScreenOfDisplay( display, DefaultScreen( display ) );
   }

   if( CHARHEIGHT == 0 ) {
      const char  *fontname;

      fontname = GetXDefault( DEFL_FONT );
      if( ( font = XLoadQueryFont( display, fontname ) ) == NULL ) {
         fprintf( stderr, "Could not load font `%s'", fontname );
         if( not IsDefault( DEFL_FONT, fontname ) ) {
            fontname = ProgDefault( DEFL_FONT );
            if( ( font = XLoadQueryFont( display, fontname ) ) == NULL ) {
               fprintf( stderr, " or `%s'\n", fontname );
               return( FALSE );
            } else
               fprintf( stderr, " using `%s' instead\n", fontname );
         } else {
            fprintf( stderr, "\n" );
            return( FALSE );
         }
      }
      CHARHEIGHT = font->max_bounds.ascent + font->max_bounds.descent;
      CHARWIDTH = font->max_bounds.width;
      descent = font->max_bounds.descent;

      InitGraphics( font->fid );
   }

   banner = ( fname != NULL and *fname != '\0' ) ? fname : wname;
   bannerLen = strlen( banner );

   if( iconW == 0 )
      iconW = CreateIconWindow( 10, 10 );

   if( window == 0 ) {
      InitWindow( TRUE, NormalState, 0, 0, 0, 0, 10, 10 );
      InitMenus();
   }


   if( not InitHandler( ConnectionNumber( display ) ) )
      return( FALSE );

   return( TRUE );
}


public int InitWindow( int firstTime, int state, Coord x, Coord y, unsigned int w, unsigned int h, Coord ix, Coord iy ) {
   XSizeHints            shint;
   XClassHint            classHint;

   XWMHints              wmh;
   XSetWindowAttributes  att;

   int                   spec, u_spec;
   static int            b;
   const char                  *geo;

   if( firstTime ) {
      b = atoi( GetXDefault( DEFL_BDRWIDTH ) );
      if( b <= 0 )
         b = atoi( ProgDefault( DEFL_BDRWIDTH ) );

      geo = ProgDefault( DEFL_GEOM );
      spec = XParseGeometry( geo, &x, &y, &w, &h );
      geo = GetXDefault( DEFL_GEOM );
      u_spec = IsDefault( DEFL_GEOM, geo ) ? FALSE : TRUE;
      if( u_spec )
         spec = XParseGeometry( geo, &x, &y, &w, &h );

      if( ( spec & ( XValue | XNegative ) ) == ( XValue | XNegative ) )
         x += WidthOfScreen( screen ) - w - 2 * b;

      if( ( spec & ( YValue | YNegative ) ) == ( YValue | YNegative ) )
         y += HeightOfScreen( screen ) - h - 2 * b;

      XWINDOWSIZE = w;
      YWINDOWSIZE = h;
   } else
      u_spec = TRUE;

   att.background_pixel = colors.white;
   att.border_pixmap = pix.gray;
   att.backing_planes = colors.black | colors.white | colors.hilite |
                        colors.traces | colors.banner_bg | colors.banner_fg;
   att.cursor = cursors.deflt;

   window = XCreateWindow( display, RootWindowOfScreen( screen ),
                           x, y, w, h, b, DefaultDepthOfScreen( screen ), InputOutput,
                           ( Visual * ) CopyFromParent,
                           ( CWBackPixel | CWBorderPixmap | CWBackingPlanes | CWCursor ), &att );

   XStoreName( display, window, wname );
   XSetIconName( display, window, wname );
   classHint.res_name  = (char*)"irsim";
   classHint.res_class = (char*)wname;
   XSetClassHint( display, window, &classHint );

   wmh.input = True;
   wmh.initial_state = state;
   wmh.icon_pixmap = pix.icon;
   wmh.icon_window = iconW;
   wmh.icon_x = ix;
   wmh.icon_y = iy;
   wmh.flags = InputHint | StateHint | IconPixmapHint | IconWindowHint |
               IconPositionHint;
   XSetWMHints( display, window, &wmh );

   shint.x = x;
   shint.y = y;
   shint.width = w;
   shint.height = h;
   shint.max_width = shint.max_height = 16000;    /* any big number */
   shint.width_inc = shint.height_inc = 1;
   GetMinWsize( &shint.min_width, &shint.min_height );
   shint.flags = ( u_spec ) ?
                 ( PMinSize | PMaxSize | PResizeInc | USPosition | USSize ) :
                 ( PMinSize | PMaxSize | PResizeInc | PPosition | PSize );
   XSetNormalHints( display, window, &shint );

   XSelectInput( display, window, ExposureMask | StructureNotifyMask |
                 KeyPressMask | ButtonPressMask | EnterWindowMask | LeaveWindowMask );
   XFlush( display );
}


public
#define	DEF_STEPS	4	/* default simulation steps per screen */

/*
 * Initialize the display times so that when first called the last time is
 * shown on the screen.  Default width is DEF_STEPS (simulation) steps.
 */
public void InitTimes( TimeType firstT, TimeType stepsize, TimeType lastT ) {
   tims.first = firstT;
   tims.last = lastT;
   tims.steps = 4 * stepsize;

   if( autoScroll or tims.start <= tims.first ) {
      if( lastT < tims.steps ) {
         tims.start = tims.first;
         tims.end = tims.start + tims.steps;
      } else {
         tims.end = lastT + 2 * stepsize;
         tims.start = tims.end - tims.steps;
         if( tims.start < tims.first ) {
            stepsize = tims.first - tims.start;
            tims.start += stepsize;
            tims.end += stepsize;
         }
      }
   }
   tims.cursor = -1;
}


/*
 * Displays the window banner.
 */
public void RedrawBanner() {
   Menu  *mp;

   FillBox( window, bannerBox, gcs.bannerBg );
   XCopyArea( display, pix.iconbox, window, gcs.bannerFg, 0, 0, 13, 13,
              iconBox.left, iconBox.top );

   StrLeft( window, banner, bannerLen, iconBox.right + 4,
            ( bannerBox.bot - 2 + CHARHEIGHT ) / 2, gcs.bannerFg );

   if( windowState.selected ) {
      if( selectBox.right > selectBox.left )
         FillBox( window, selectBox, gcs.select );
      FillAREA( window, bannerBox.left, bannerBox.bot - 1, bannerBox.right -
                bannerBox.left + 1, 2, gcs.border );
      XSetWindowBorder( display, window, colors.border );
   } else {
      FillAREA( window, bannerBox.left, bannerBox.bot - 1, bannerBox.right -
                bannerBox.left + 1, 2, gcs.gray );
      XSetWindowBorderPixmap( display, window, pix.gray );
   }

   for( mp = menu; mp->str != NULL; mp++ ) {
      StrCenter( window, mp->str, mp->len, mp->box.left, mp->box.right,
                 mp->box.bot, gcs.bannerFg );
   }

   XCopyArea( display, pix.sizebox, window, gcs.bannerFg, 0, 0, 13, 13,
              sizeBox.left, sizeBox.top );
}


public void WindowCrossed( int selected ) {
   GC  color;

   if( selected == windowState.selected )
      return;

   windowState.selected = selected;

   if( selected )
      XSetWindowBorder( display, window, colors.border );
   else
      XSetWindowBorderPixmap( display, window, pix.gray );

   if( windowState.tooSmall )
      return;

   if( selectBox.right > selectBox.left ) {
      if( selected )
         FillBox( window, selectBox, gcs.select );
      else
         FillBox( window, selectBox, gcs.bannerBg );
   }
   FillAREA( window, bannerBox.left, bannerBox.bot - 1, XWINDOWSIZE - 1, 2,
             selected ? gcs.border : gcs.gray );
}


public void RedrawSmallW() {
   static  const char  *msg = "I'm too small";
   Coord   y;
   int     len;

   XClearWindow( display, window );
   len = strlen( msg );
   y = ( YWINDOWSIZE - CHARHEIGHT ) / 2;
   StrCenter( window, msg, len, 0, XWINDOWSIZE, y, gcs.black );
}


public void RedrawTimes() {
   char   s[ 20 ];
   int    len;
   Coord  x;

   FillAREA( window, 0, timesBox.top, XWINDOWSIZE,
             timesBox.bot - timesBox.top + 1, gcs.white );

   ( void ) sprintf( s, " %.2f ", d2ns( tims.start ) );
   len = strlen( s );
   StrLeft( window, s, len, timesBox.left, timesBox.bot, gcs.white );
   ( void ) sprintf( s, " %.2f ", d2ns( tims.end ) );
   len = strlen( s );
   StrRight( window, s, len, timesBox.right - 1, timesBox.bot, gcs.white );
   if( tims.cursor >= 0 )
      ( void ) sprintf( s, "%.2f", d2ns( tims.cursor ) );
   else
      ( void ) strcpy( s, "-" );

   len = strlen( s );
   StrCenter( window, s, len, timesBox.left, timesBox.right, timesBox.bot,
              gcs.black );
}


public void UpdateTimes( TimeType start, TimeType end ) {
   static TimeType  ostart, oend;
   static int       slen, elen;
   int              len;
   char             s[20];

   if( start != ostart ) {
      ( void ) sprintf( s, " %.2f ", d2ns( start ) );
      len = strlen( s );
      if( len < slen )
         FillAREA( window, timesBox.left + ( len * CHARWIDTH ),
                   timesBox.bot - CHARHEIGHT - 1, ( slen - len ) * CHARWIDTH,
                   timesBox.bot - timesBox.top + 1, gcs.white );
      StrLeft( window, s, len, timesBox.left, timesBox.bot, gcs.white );
      ostart = start;
      slen = len;
   }

   if( end != oend ) {
      ( void ) sprintf( s, " %.2f ", d2ns( end ) );
      len = strlen( s );
      if( len < elen )
         FillAREA( window, timesBox.right - ( elen * CHARWIDTH ),
                   timesBox.bot - CHARHEIGHT - 1, ( elen - len ) * CHARWIDTH,
                   timesBox.bot - timesBox.top + 1, gcs.white );
      StrRight( window, s, len, timesBox.right, timesBox.bot, gcs.white );
      oend = len;
      elen = len;
   }
}


/*
 * Redraw signal names.
 */
public void RedrawNames( BBox rb ) {
   Coord  x, y;
   Trptr  t;
   int    i;

   rb.left = max( rb.left, namesBox.left );
   rb.right = min( rb.right, namesBox.right );
   rb.top = max( namesBox.top, rb.top );
   rb.bot = min( namesBox.bot, rb.bot );
   FillBox( window, rb, gcs.white );

   for( i = traces.disp, t = traces.first; i != 0; i--, t = t->next )
      if( rb.top <= t->bot )
         break;

   x = namesBox.right - 2;
   while( i != 0 and rb.bot >= t->top ) {
      y = ( t->bot + t->top + CHARHEIGHT ) / 2;
      StrRight( window, t->name, t->len, x, y, gcs.black );
      if( t == selectedTrace )
         UnderlineTrace( t, gcs.black );
      i--;
      t = t->next;
   }
}

#define	CursorVisible( T1, T2 )	(tims.cursor >= (T1) and tims.cursor <= (T2) )

/*
 * This will redraw the missing parts of the traces.  Used to selectivelly
 * repaint traces or during Exposure events.
 */
public void RedrawTraces( BBox  *box ) {
   TimeType        t1, t2, tc;
   BBox            bg;
   register Trptr  t;
   register int    i;

   t1 = XToTime( box->left ) - 1;
   if( t1 < tims.start ) {
      t1 = tims.start;
      bg.left = traceBox.left;
   } else
      bg.left = box->left;

   t2 = XToTime( box->right );
   if( t2 < 0 ) {
      t2 = tims.end;
      bg.right = traceBox.right;
   } else {
      bg.right = box->right;
      if( t2 < tims.end )
         t2++;
   }

   tc = t2;
   if( t2 > tims.last )
      t2 = tims.last;

   bg.top = max( box->top, traceBox.top );
   bg.bot = min( box->bot, traceBox.bot );
   if( CursorVisible( t1, tc ) )
      EraseCursor();

   FillBox( window, bg, gcs.black );

   for( i = traces.disp, t = traces.first; i != 0; i--, t = t->next )
      if( box->top <= t->bot )
         break;

   while( i != 0 and box->bot >= t->top ) {
      if( IsVector( t ) )
         DrawVector( t, t1, t2, FALSE );
      else
         DrawSignal( t, t1, t2 );
      i--;
      t = t->next;
   }
   if( CursorVisible( t1, tc ) )
      DrawCursor();
}


/*
 * Update the cache (begining of window and cursor) for traces that just
 * became visible ( or were just added ).
 */
public void UpdateTraceCache( int first_trace ) {
   register Trptr     t;
   register hptr      h,p;
   register int       n, i;
   register TimeType  startT, cursT;

   startT = tims.start;
   cursT = max( tims.cursor, tims.first );
   for( t = traces.first, n = 0; n < traces.disp; n++, t = t->next ) {
      if( n < first_trace )
         continue;

      if( t->vector ) {
         for( i = t->n.vec->nbits - 1; i >= 0; i-- ) {
            hptr  nexth;

            p = t->cache[i].wind;
            h = t->cache[i].cursor;
            NEXTH( nexth, h );
            if( h->time > cursT or nexth->time <= cursT ) {
               if( p->time <= cursT )	/* whatever is closer */
                  t->cache[i].cursor = p;
               else
                  t->cache[i].cursor = ( hptr )&( t->n.vec->nodes[i]->head );
            }
            if( startT <= p->time )			/* go back */
               p = ( hptr ) &( t->n.vec->nodes[i]->head );

            NEXTH( h, p );
            while( h->time < startT ) {
               p = h;
               NEXTH( h, h );
            }
            t->cache[i].wind = p;

            p = t->cache[i].cursor;
            NEXTH( h, p );
            while( h->time <= cursT ) {
               p = h;
               NEXTH( h, h );
            }
            t->cache[i].cursor = p;
         }
      } else {
         hptr  nexth;

         p = t->cache[0].wind;
         h = t->cache[0].cursor;
         NEXTH( nexth, h );
         if( h->time > cursT or nexth->time <= cursT ) {
            if( p->time <= cursT )
               t->cache[0].cursor = p;
            else
               t->cache[0].cursor = ( hptr ) &( t->n.nd->head );
         }

         if( startT <= p->time )
            p = ( hptr ) &( t->n.nd->head );

         NEXTH( h, p );
         while( h->time < startT ) {
            p = h;
            NEXTH( h, h );
         }
         t->cache[0].wind = p;

         p = t->cache[0].cursor;
         NEXTH( h, p );
         while( h->time <= cursT ) {
            p = h;
            NEXTH( h, h );
         }
         t->cache[0].cursor = p;
      }
   }
}


private	TimeType  lastStart;		/* last redisplay starting time */


public void FlushTraceCache() {
   lastStart = max_time;
}


/*
 * Draw the traces horizontally from time1 to time2.
 */
public void DrawTraces( TimeType t1, TimeType t2 ) {
   TimeType         endT;
   register Trptr   t;
   int              nt;

   if( t1 == tims.start )
      FillBox( window, traceBox, gcs.black );
   else if( colors.disj == 0 and CursorVisible( tims.start, t2 ) )
      EraseCursor();

   if( tims.start != lastStart ) {	/* Update history cache */
      int                begin;
      register TimeType  startT;
      register int       n, i;
      register hptr      h, p;

      startT = tims.start;
      begin = ( startT < lastStart );
      for( t = traces.first, n = traces.disp; n != 0; n--, t = t->next ) {
         if( t->vector ) {
            for( i = t->n.vec->nbits - 1; i >= 0; i-- ) {
               p = begin ? ( hptr ) &( t->n.vec->nodes[i]->head ) : t->cache[i].wind;
               NEXTH( h, p );
               while( h->time < startT ) {
                  p = h;
                  NEXTH( h, h );
               }
               t->cache[i].wind = p;
            }
         } else {
            p = begin ? ( hptr ) &( t->n.nd->head ) : t->cache[0].wind;
            NEXTH( h, p );
            while( h->time < startT ) {
               p = h;
               NEXTH( h, h );
            }
            t->cache[0].wind = p;
         }
      }
      lastStart = tims.start;
   }

   endT = min( t2, tims.last );

   for( t = traces.first, nt = traces.disp; nt != 0; nt--, t = t->next ) {
      if( IsVector( t ) )
         DrawVector( t, t1, endT, ( t1 != tims.start ) ? TRUE : FALSE );
      else
         DrawSignal( t, t1, endT );
   }
   if( CursorVisible( tims.start, t2 ) )
      DrawCursor();
}


/*
 * Draw a 1 bit trace.
 */
private void DrawSignal( Trptr t, TimeType t1, TimeType t2 ) {
   register hptr  h;
   register int   val, change;
   int            x1, x2;

   if( t1 >= tims.last )
      return;

   h = t->cache[0].wind;
   if( t1 != tims.start ) {
      register hptr  n;

      NEXTH( n, h );
      while( n->time < t1 ) {
         h = n;
         NEXTH( n, n );
      }
   }

   x1 = TimeToX( t1 );
   while( t1 < t2 ) {
      val = h->val;
      while( h->time < t2 and h->val == val )
         NEXTH( h, h );
      if( h->time > t2 ) {
         change = FALSE;
         t1 = t2;
      } else {
         change = ( h->val != val );
         t1 = h->time;
      }
      x2 = TimeToX( t1 );
      switch( val ) {
      case LOW :
         HLine( window, x1, x2, t->bot, gcs.traceFg );
         break;
      case HIGH :
         HLine( window, x1, x2, t->top, gcs.traceFg );
         break;
      case X :
         if( x1 > traceBox.left + 1 )
            x1++;
         FillAREA( window, x1, t->top, x2 - x1 + 1, t->bot - t->top + 1,
                   gcs.xpat );
         break;
      }
      if( change )
         VLine( window, x2, t->bot, t->top, gcs.traceFg );
      x1 = x2;
   }
}


public	hptr    tmpHBuff[ 400 ];

/*
 * Draw bus trace.
 */
private void DrawVector( Trptr t, TimeType t1, TimeType t2, int clr_bg ) {
   hptr      *start, *changes;
   TimeType  firstChange;
   int       x1, x2, xx, mid, nbits, strlen, strwidth;

   if( t1 >= tims.last )
      return;

   nbits = t->n.vec->nbits;
   start = tmpHBuff;
   changes = &( tmpHBuff[ nbits ] );
   strlen = ( nbits + t->bdigit - 1 ) / t->bdigit;
   strwidth = CHARWIDTH * strlen + 1;

   {
      register hptr  h, *s;
      register int   n, val;

      s = start;			/* initialize start array */
      if( t1 != tims.start ) {
         register hptr  p;

         firstChange = tims.start;
         for( n = nbits - 1; n >= 0; n-- ) {
            p = t->cache[n].wind;
            val = p->val;
            NEXTH( h, p );
            while( h->time < t1 ) {
               if( h->val != val ) {
                  if( h->time > firstChange )
                     firstChange = h->time;
                  val = h->val;
               }
               p = h;
               NEXTH( h, h );
            }
            s[n] = p;
         }
      } else {
         firstChange = tims.start;
         for( n = nbits - 1; n >= 0; n-- )
            s[n] = t->cache[n].wind;
      }

      {
         /* Initialize changes array */
         register hptr      *ch = changes;
         register TimeType  tm = tims.end;

         for( n = nbits - 1; n >= 0; n-- ) {
            h = s[n];
            val = h->val;
            while( h->time < tm and h->val == val )
               NEXTH( h, h );
            ch[n] = h;
         }
      }
   }

   mid = ( t->top + t->bot + CHARHEIGHT ) / 2;
   xx = TimeToX( t1 );
   x2 = TimeToX( t2 );
   x1 = TimeToX( firstChange );
   HLine( window, xx, x2, t->top, gcs.traceFg );
   HLine( window, xx, x2, t->bot, gcs.traceFg );
   if( clr_bg and t1 != tims.start and ( xx - x1 ) > strwidth )
      FillAREA( window, x1+1, mid - CHARHEIGHT+1, xx - x1+1, CHARHEIGHT,
                gcs.traceBg );

   while( t1 < t2 ) {
      {
         /* find nearest change in time */
         register hptr  *ch;
         register int   n;

         t1 = tims.end + 1;
         for( ch = changes, n = nbits - 1; n >= 0; n-- ) {
            if( ch[n]->time < t1 )
               t1 = ch[n]->time;
         }
      }

      if( t1 <= t2 ) {		/* change before t2 => draw it */
         register int  n;

         x2 = TimeToX( t1 );
         n = ( x2 == traceBox.left+1 ) ? 2 : ( x2 == traceBox.right-1 ) ? 1 : 0;
         VLine( window, x2, t->bot, t->top, gcs.traceFg );
         XCopyArea( display, pix.tops[n], window, gcs.traceBg, 0, 0, 3, 2,
                    x2 - 1, t->top );
         XCopyArea( display, pix.bots[n], window, gcs.traceBg, 0, 0, 3, 2,
                    x2 - 1, t->bot - 1 );
      } else {			/* change after t2 */
         register TimeType  tm;

         tm = min( t1, min( tims.end, tims.last ) );
         x2 = TimeToX( tm );
      }

      if( x2 - x1 > strwidth ) {
         char  *str;
         str = HistToStr( start, nbits, t->bdigit, 1 );
         StrCenter( window, str, strlen, x1, x2, mid, gcs.traceFg );
      }

      {
         register hptr      h;
         register hptr      *ch, *s;
         register int       n, val;
         register TimeType  tm = tims.end;

         for( s = start, ch = changes, n = nbits - 1; n >= 0; n-- ) {
            if( ch[n]->time == t1 ) {
               h = s[n] = ch[n];
               val = h->val;
               while( h->time < tm and h->val == val )
                  NEXTH( h, h );
               ch[n] = h;
            }
         }
      }
      x1 = x2;
   }
}



private void UpdateTraces( TimeType start, TimeType end ) {
   if( not ( windowState.iconified or windowState.tooSmall ) ) {
      UpdateScrollBar();
      RedrawTimes();
      DrawTraces( start, end );
   }
}


private void ScrollTraces( TimeType endT ) {
   tims.start = endT - tims.steps / 2;
   tims.end = tims.start + tims.steps;
   UpdateTraces( tims.start, endT );
}


/*
 * Update the trace window so that endT is shown.  If the update fits in the
 * window, simply draw the missing parts.  Otherwise scroll the traces,
 * centered around endT.
 */
public void UpdateWindow( TimeType  endT ) {
   TimeType  lastT;

   DisableInput();

   if( freezeWindow ) {
      updatePending = TRUE;
      updPendTime = endT;
   } else {
      lastT = tims.last;
      tims.last = endT;

      if( endT <= tims.end ) {
         if( lastT >= tims.start )
            UpdateTraces( lastT, endT );
         else if( autoScroll )
            ScrollTraces( endT );
         else if( endT > tims.start )
            UpdateTraces( tims.start, endT );
      } else {				/* endT > tims.end */
         if( autoScroll )
            ScrollTraces( endT );
         else if( lastT < tims.end )
            UpdateTraces( lastT, tims.end );
      }
   }
   EnableInput();
}


/*
 * Erase/Redraw the cursor.
 */
private void EraseCursor() {
   Coord  x;

   x = TimeToX( tims.cursor );
   FillAREA( window, x, traceBox.top, 1, traceBox.bot - traceBox.top,
             gcs.curs_off );
}


private void  DrawCursor() {
   Coord  x;

   x = TimeToX( tims.cursor );
   FillAREA( window, x, traceBox.top, 1, traceBox.bot - traceBox.top,
             gcs.curs_on );
}


public void DoCursor( XButtonEvent  *ev ) {
   Trptr     t;
   TimeType  time;
   char      tbuff[15];

   if( not ( ev->state & ShiftMask ) ) {
      MoveCursorToPos( ev->x );
      return;
   }

   t = GetYTrace( ev->y );
   time = XToTime( ev->x );
   if( t == NULL or time < 0 or time > tims.last ) {
      XBell( display, 0 );
      return;
   }
   ( void ) sprintf( tbuff, "%.2f", d2ns( time ) );
   PRINTF( "\n%s @ %s: value=", t->name, tbuff );
   {
      register hptr  h, p;
      register int   n;
      register char  *val, *inp;
      int            nbits;

      val = ( char * ) tmpHBuff;
      if( IsVector( t ) )
         nbits = t->n.vec->nbits;
      else
         nbits = 1;

      inp = &( val[nbits] );
      *inp++ = '\0';
      inp[nbits] = '\0';
      for( n = nbits - 1; n >= 0; n-- ) {
         p = t->cache[n].wind;
         NEXTH( h, p );
         while( h->time <= time ) {
            p = h;
            NEXTH( h, h );
         }
         val[n] = "0X 1"[ p->val ];
         inp[n] = "-i"[p->inp];
      }
      PRINTF( "%s, input=%s", val, inp );
   }
}


private	const char  *StrMap[] = { "0", "X", "", "1" };


/*
 * Display signal values under cursor.
 */
public void DrawCursVal( BBox rb ) {
   Coord        y;
   Trptr        t;
   int          i, len;
   const char         *val;

   rb.left = max( rb.left, cursorBox.left );
   rb.right = min( rb.right, cursorBox.right );
   rb.top = max( cursorBox.top, rb.top );
   rb.bot = min( cursorBox.bot, rb.bot );
   FillBox( window, rb, gcs.white );

   if( tims.cursor < tims.first or tims.cursor > tims.last )
      return;

   for( i = traces.disp, t = traces.first; i != 0; i--, t = t->next )
      if( rb.top <= t->bot )
         break;

   while( i != 0 and rb.bot >= t->top ) {
      y = ( t->bot + t->top + CHARHEIGHT ) / 2;
      val = IsVector( t ) ?
            HistToStr( &( t->cache[0].cursor ), t->n.vec->nbits, t->bdigit, 2 ) :
            StrMap[ t->cache[0].cursor->val ];

      len = strlen( val );
      StrCenter( window, val, len, cursorBox.left, cursorBox.right, y,
                 gcs.black );
      i--;
      t = t->next;
   }
}


public void ExpandCursVal( Trptr  t ) {
   char          *val;
   int           nbits;

   nbits = IsVector( t ) ? t->n.vec->nbits : 1;
   val = HistToStr( &( t->cache[0].cursor ), nbits, 1, 2 );

   PRINTF( "\n %s : value=%s", t->name, val );
   {
      register int    n;
      register Cache  *c;
      register char   *s;

      for( n = 0, s = val, c = t->cache; n < nbits; n++ )
         *s++ = ( c[n].cursor->inp ) ? 'i' : '-';
   }
   PRINTF( "  input=%s", val );
}

/*
 * Redraw any region that overlaps the redraw-box.
 */
public void RedrawWindow( BBox box ) {
   if( Intersect( bannerBox, box ) )
      RedrawBanner();
   if( Intersect( timesBox, box ) )
      RedrawTimes();
   if( Intersect( namesBox, box ) )
      RedrawNames( box );
   if( Intersect( scrollBox, box ) )
      DrawScrollBar( TRUE );
   if( Intersect( cursorBox, box ) )
      DrawCursVal( box );
   if( Intersect( textBox, box ) )
      RedrawText();
   if( Intersect( traceBox, box ) )
      RedrawTraces( &box );
}


void MoveCursorToPos( Coord x ) {
   register int       i;
   register Trptr     t;
   register TimeType  time;
   char               s[ 20 ];
   static int         olen = 1;

   time = XToTime( x );
   if( time == tims.cursor or time < tims.start or time > tims.end )
      return;

   if( CursorVisible( tims.start, tims.end ) )
      EraseCursor();

   tims.cursor = time;
   DrawCursor();
   ( void ) sprintf( s, "%.2f", d2ns( time ) );
   i = strlen( s );
   if( i < olen ) {
      FillAREA( window, ( timesBox.left+timesBox.right+( olen * CHARWIDTH ) )/2,
                timesBox.bot - CHARHEIGHT - 1, olen * CHARWIDTH,
                timesBox.bot - timesBox.top + 1, gcs.white );
   }
   StrCenter( window, s, i, timesBox.left, timesBox.right, timesBox.bot,
              gcs.black );
   for( i = traces.disp, t = traces.first; i != 0; i--, t = t->next ) {
      register hptr   h, p;

      if( IsVector( t ) ) {
         register int  n;

         for( n = t->n.vec->nbits - 1; n >= 0; n-- ) {
            p = t->cache[n].wind;
            NEXTH( h, p );
            while( h->time <= time ) {
               p = h;
               NEXTH( h, h );
            }
            t->cache[n].cursor = p;
         }
      } else {
         p = t->cache[0].wind;
         NEXTH( h, p );
         while( h->time <= time ) {
            p = h;
            NEXTH( h, h );
         }
         t->cache[0].cursor = p;
      }
   }
   DrawCursVal( cursorBox );
}

